package com.imooc.follow.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.imooc.commons.constant.ApiConstant;
import com.imooc.commons.constant.RedisKeyConstant;
import com.imooc.commons.exception.ParameterException;
import com.imooc.commons.model.domain.ResultInfo;
import com.imooc.commons.model.pojo.Follow;
import com.imooc.commons.model.vo.ShortDinerInfo;
import com.imooc.commons.model.vo.SignInDinerInfo;
import com.imooc.commons.utils.AssertUtil;
import com.imooc.commons.utils.ResultInfoUtil;
import com.imooc.follow.mapper.FollowMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 关注/取关 业务层
 *
 * @author gcq
 * @Create 2022-11-12
 */
@Service
public class FollowService {

    @Resource
    private FollowMapper followMapper;

    @Value("${service.name.ms-oauth-server}")
    private String oauthServerName;

    @Value("${service.name.ms-diners-server}")
    private String dinerServerName;

    @Value("${service.name.ms-feeds-server}")
    private String feedsServerName;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 获取粉丝列表
     *
     * @param dinerId 食客Id
     * @return 粉丝列表Id
     */
    public Set<Integer> findFollowers(Integer dinerId) {
        AssertUtil.isTrue(dinerId == null, "dinerId不能为空");
        Set<Integer> members = redisTemplate.opsForSet().members(RedisKeyConstant.followers.getKey() + dinerId);
        return members;
    }

    /**
     * 共同关注列表
     *
     * @param dinerId
     * @param accessToken
     * @param path
     * @return
     */
    public ResultInfo findCommonsFriends(Integer dinerId, String accessToken, String path) {
        // 是否选择了查看对象
        AssertUtil.isTrue(dinerId == null || dinerId < 1, "请选择要查看的人");
        // 获取登录用户信息
        SignInDinerInfo signInDinerInfo = loadSignInDinerInfo(accessToken);

        // 获取登录用户关注的列表
        String loginDinerKey = RedisKeyConstant.following.getKey() + signInDinerInfo.getId();

        // 获取登录用户查看对象的关注信息
        String dinerKey = RedisKeyConstant.following.getKey() + dinerId;

        // 求出交集
        Set<Integer> dinerIds = redisTemplate.opsForSet().intersect(loginDinerKey, dinerKey);
        if(dinerIds == null || dinerIds.isEmpty()) {
            return ResultInfoUtil.buildSuccess(path, new ArrayList<ShortDinerInfo>());
        }

        // 通过调用食客服务获取交集用户信息
        ResultInfo resultInfo = restTemplate.getForObject(dinerServerName + "findByIds?access_token={accessToken}&ids={ids}", ResultInfo.class, accessToken, StrUtil.join(",", dinerIds));
        if(resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            resultInfo.setPath(path);
            return resultInfo;
        }

        // 处理结果集
        List<LinkedHashMap> dinnerInfoMaps = (ArrayList)resultInfo.getData();
        List<ShortDinerInfo> dinerInfos = dinnerInfoMaps.stream()
                .map(diner -> BeanUtil.fillBeanWithMap(diner, new ShortDinerInfo(), false))
                .collect(Collectors.toList());
        return ResultInfoUtil.buildSuccess(path, dinerInfos);
    }

    /**
     * 用户关注或取关
     *
     * @param followDinerId 关注用户Id
     * @param isFollowId    是否关注 1=关注 0=取关
     * @param accessToken   登录用户token
     * @param path          访问地址
     * @return 结果
     */
    public ResultInfo follow(Integer followDinerId, Integer isFollowId, String accessToken, String path) {
        // 验证关注用户Id是否正确
        AssertUtil.isTrue(followDinerId == null || followDinerId < 1, "关注用户Id错误");

        // 获取当前登录用户信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);

        // 获取当前用户关注信息
        Follow follow = followMapper.selectFollow(dinerInfo.getId(), followDinerId);

        // 如果没有关注信息，且需要进行关注操作  = 关注操作
        if (follow == null && isFollowId == 1) {
            // 关注操作
            int count = followMapper.save(dinerInfo.getId(), followDinerId);
            if (count == 1) {
                // 添加关注列表至 redis
                addToRedisSet(dinerInfo.getId(), followDinerId);
                // 保存feed
                sendSaveOrRemoveFeed(followDinerId, accessToken, 1);
            }
            return ResultInfoUtil.buildSuccess("关注成功", path);
        }

        // 如果有关注信息，且前为关注状态，且要进行取关 = 取关操作
        if (follow != null && follow.getIsValid() == 1 && isFollowId == 0) {
            // 取消关注
            int count = followMapper.update(follow.getId(), isFollowId);
            if (count == 1) {
                // 删除 redis 中关注列表
                removeFormRedisSet(dinerInfo.getId(), followDinerId);
                // 移除
                sendSaveOrRemoveFeed(followDinerId, accessToken, 0);
            }
            return ResultInfoUtil.buildSuccess("取关成功", path);
        }

        // 如果有关注信息，且当前为取关状态，需要重新进行关注 = 重新关注
        if (follow != null && follow.getIsValid() == 0 && isFollowId == 1) {
            // 重新关注
            int count = followMapper.update(follow.getId(), isFollowId);
            if (count == 1) {
                // 添加关注列表至 redis
                addToRedisSet(dinerInfo.getId(), followDinerId);
                // 保存feed
                sendSaveOrRemoveFeed(followDinerId, accessToken, 1);
            }
            return ResultInfoUtil.buildSuccess("重新关注成功", path);
        }

        return ResultInfoUtil.buildSuccess("操作成功", path);
    }


    /**
     * 发送请求添加或移除关注人的Feed列表
     *
     * @param followingDinerId 关注的好友的 ID
     * @param accessToken      访问token
     * @param type              1 关注 0 取关
     */
    private void sendSaveOrRemoveFeed(Integer followingDinerId, String accessToken, int type){
        String feedsUpdateUrl = feedsServerName + "updateFollowingFeeds/" + followingDinerId
                + "?access_token=" + accessToken + "&type=" + type;
        // 构建请求头
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        // 构建请求体
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("type", type);
        HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, httpHeaders);
        restTemplate.postForEntity(feedsUpdateUrl, entity, ResultInfo.class);
    }

    /**
     * 更新关注信息
     *
     * @param dinerId       用户Id
     * @param followDinerId 被关注用户Id
     */
    private void removeFormRedisSet(Integer dinerId, Integer followDinerId) {
        // 删除用户关注列表
        redisTemplate.opsForSet().remove(RedisKeyConstant.following.getKey() + dinerId, followDinerId);
        // 删除被关注用户粉丝列表
        redisTemplate.opsForSet().remove(RedisKeyConstant.followers.getKey() + followDinerId, dinerId);
    }

    /**
     * 添加关注信息至Redis
     *
     * A 关注 B A就是B的粉丝
     * 你关注了我，你就是我的粉丝
     * 1、关注集合中添加 关注人的Id
     * 2、粉丝集合添加 粉丝的Id
     *
     * @param dinerId       用户Id
     * @param followDinerId 被关注用户Id
     */
    private void addToRedisSet(Integer dinerId, Integer followDinerId) {
        // 用户关注了谁
        redisTemplate.opsForSet().add(RedisKeyConstant.following.getKey() + dinerId, followDinerId);
        // 被关注用户的粉丝列表
        redisTemplate.opsForSet().add(RedisKeyConstant.followers.getKey() + followDinerId, dinerId);
    }

    /**
     * 获取登录用户信息
     *
     * @param accessToken 访问token
     * @return 登录用户信息
     */
    private SignInDinerInfo loadSignInDinerInfo(String accessToken) {
        AssertUtil.mustLogin(accessToken);

        String url = oauthServerName + "/user/me?access_token={accessToken}";
        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getMessage());
        }
        SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(), new SignInDinerInfo(), false);
        return dinerInfo;
    }


}